#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
词法解析器批量测试脚本
此脚本会查找 ./testcases/tests 下所有的 .in 文件，运行词法解析器，并与对应的 .out 文件比较
"""

import os
import sys
import subprocess
import shutil
import tempfile
import difflib
import re
from typing import List, Tuple, Dict, Set, Optional
from colorama import Fore, Style, init

# 初始化 colorama，用于跨平台彩色输出
init()

# 工作目录（当前脚本所在目录）
WORKING_DIR = os.path.abspath(os.getcwd())

def print_colored(text: str, color: str = Fore.WHITE) -> None:
    """彩色打印文本"""
    print(f"{color}{text}{Style.RESET_ALL}")

def check_compiler_exists() -> str:
    """检查编译器是否存在"""
    compiler_path = os.path.join(WORKING_DIR, "target", "debug", "compiler.exe")
    if not os.path.isfile(compiler_path):
        # 在 Linux/Mac 上检查没有 .exe 后缀的版本
        compiler_path = os.path.join(WORKING_DIR, "target", "debug", "compiler")
        if not os.path.isfile(compiler_path):
            print_colored("错误: 找不到编译器可执行文件", Fore.RED)
            print_colored("请先编译项目: cargo build", Fore.YELLOW)
            sys.exit(1)
    return compiler_path

def check_testcases_dir() -> str:
    """检查测试用例目录是否存在"""
    testcases_dir = os.path.join(WORKING_DIR, "testcases", "tests")
    if not os.path.isdir(testcases_dir):
        print_colored(f"错误: 找不到测试文件目录: {testcases_dir}", Fore.RED)
        sys.exit(1)
    return testcases_dir

def get_input_files(testcases_dir: str) -> List[str]:
    """获取所有 .in 文件的路径"""
    input_files = []
    for filename in os.listdir(testcases_dir):
        if filename.endswith(".in"):
            input_files.append(os.path.join(testcases_dir, filename))
    return input_files

def normalize_content(content: str) -> str:
    """规范化内容，统一行尾"""
    return content.replace('\r\n', '\n').strip()

def run_compiler(compiler_path: str, input_file: str, output_file: str) -> None:
    """运行编译器，处理输入文件并将输出保存到输出文件"""
    try:
        result = subprocess.run(
            [compiler_path, input_file],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            encoding='utf-8'
        )
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(result.stdout)
    except Exception as e:
        print_colored(f"运行编译器时出错: {e}", Fore.RED)
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(f"ERROR: {str(e)}")

def analyze_differences(expected: str, actual: str) -> Dict:
    """分析两个输出之间的差异"""
    results = {}
    
    # 分割成行进行比较
    expected_lines = expected.split('\n')
    actual_lines = actual.split('\n')
    
    results['expected_lines'] = len(expected_lines)
    results['actual_lines'] = len(actual_lines)
    
    # 检查是否包含错误信息
    results['expected_has_error'] = bool(re.search(r"Error type A", expected))
    results['actual_has_error'] = bool(re.search(r"Error type A", actual))
    
    # 计算差异
    diffs = []
    min_lines = min(len(expected_lines), len(actual_lines))
    
    # 逐行比较
    for i in range(min_lines):
        if expected_lines[i] != actual_lines[i]:
            diffs.append({
                'line_number': i + 1,
                'expected': expected_lines[i],
                'actual': actual_lines[i]
            })
    
    # 检查缺失或多余的行
    if len(expected_lines) > len(actual_lines):
        for i in range(len(actual_lines), len(expected_lines)):
            diffs.append({
                'line_number': i + 1,
                'expected': expected_lines[i],
                'actual': None  # 表示实际输出中没有这一行
            })
    elif len(actual_lines) > len(expected_lines):
        for i in range(len(expected_lines), len(actual_lines)):
            diffs.append({
                'line_number': i + 1,
                'expected': None,  # 表示预期输出中没有这一行
                'actual': actual_lines[i]
            })
    
    results['diffs'] = diffs
    
    # 进行可能的问题分析
    problems = []
    
    # 检查标点符号问题
    expected_punctuation = bool(re.search(r"[\.,;]\s*$", expected))
    actual_punctuation = bool(re.search(r"[\.,;]\s*$", actual))
    if expected_punctuation != actual_punctuation:
        problems.append("行尾标点符号不匹配（如句号、逗号等）")
    
    # 检查空格问题
    if re.sub(r"\s+", "", expected) == re.sub(r"\s+", "", actual):
        problems.append("空白字符差异（空格、制表符、换行符等）")
    
    # 检查大小写问题
    if re.sub(r"\s+", "", expected).lower() == re.sub(r"\s+", "", actual).lower():
        problems.append("大小写差异")
    
    # 检查错误信息格式问题
    if results['expected_has_error'] and results['actual_has_error']:
        expected_errors = re.findall(r"Error type A at Line \d+: .*", expected)
        actual_errors = re.findall(r"Error type A at Line \d+: .*", actual)
        
        if len(expected_errors) != len(actual_errors):
            problems.append("错误信息数量不匹配")
        
        # 检查行号是否匹配
        expected_line_nums = re.findall(r"Line (\d+)", expected)
        actual_line_nums = re.findall(r"Line (\d+)", actual)
        
        if expected_line_nums != actual_line_nums:
            problems.append("行号不匹配")
    
    results['problems'] = problems
    return results

def test_file(compiler_path: str, input_file: str, expected_output_file: str, temp_dir: str) -> Tuple[bool, Dict]:
    """测试单个文件并返回结果"""
    basename = os.path.basename(input_file).replace(".in", "")
    temp_output_file = os.path.join(temp_dir, f"{basename}.out")
    
    # 运行编译器处理输入文件
    run_compiler(compiler_path, input_file, temp_output_file)
    
    # 读取预期输出和实际输出
    try:
        with open(expected_output_file, 'r', encoding='utf-8') as f:
            expected = normalize_content(f.read())
        
        with open(temp_output_file, 'r', encoding='utf-8') as f:
            actual = normalize_content(f.read())
        
        # 比较结果
        if expected == actual:
            return True, {}
        else:
            # 分析差异
            return False, analyze_differences(expected, actual)
    except Exception as e:
        print_colored(f"比较输出时出错: {e}", Fore.RED)
        return False, {'error': str(e)}

def main():
    """主函数"""
    # 检查编译器是否存在
    compiler_path = check_compiler_exists()
    
    # 检查测试用例目录
    testcases_dir = check_testcases_dir()
    
    # 获取所有输入文件
    input_files = get_input_files(testcases_dir)
    
    print_colored(f"发现 {len(input_files)} 个测试文件", Fore.CYAN)
    print_colored("开始测试...", Fore.CYAN)
    print_colored("=" * 40, Fore.CYAN)
    
    # 创建临时目录
    temp_dir = tempfile.mkdtemp(prefix="lexer_test_")
    
    # 统计结果
    passed_tests = 0
    failed_tests = 0
    total_tests = len(input_files)
    
    try:
        # 测试每个文件
        for input_file in input_files:
            basename = os.path.basename(input_file).replace(".in", "")
            expected_output_file = input_file.replace(".in", ".out")
            
            # 检查预期输出文件是否存在
            if not os.path.isfile(expected_output_file):
                print_colored(f"警告: 找不到输出文件 {expected_output_file}，跳过测试 {basename}", Fore.YELLOW)
                continue
            
            print(f"测试 {basename}...", end="")
            sys.stdout.flush()
            
            # 测试文件
            passed, results = test_file(compiler_path, input_file, expected_output_file, temp_dir)
            
            if passed:
                print_colored(" 通过!", Fore.GREEN)
                passed_tests += 1
            else:
                print_colored(" 失败!", Fore.RED)
                failed_tests += 1
                
                # 显示差异分析
                print_colored("  差异分析:", Fore.YELLOW)
                
                expected_lines = results.get('expected_lines', 0)
                actual_lines = results.get('actual_lines', 0)
                
                print_colored(f"  预期输出: {expected_lines} 行", Fore.YELLOW)
                print_colored(f"  实际输出: {actual_lines} 行", Fore.YELLOW)
                
                if expected_lines != actual_lines:
                    print_colored("  行数不匹配，这可能是由于词法错误的检测或处理方式不同导致的", Fore.YELLOW)
                
                # 检查错误信息是否匹配
                expected_has_error = results.get('expected_has_error', False)
                actual_has_error = results.get('actual_has_error', False)
                
                if expected_has_error != actual_has_error:
                    if expected_has_error:
                        print_colored("  预期输出中包含词法错误信息，但实际输出中没有", Fore.YELLOW)
                        print_colored("  可能原因: 词法错误检测不完整，或者格式不匹配", Fore.YELLOW)
                    else:
                        print_colored("  实际输出中包含词法错误信息，但预期输出中没有", Fore.YELLOW)
                        print_colored("  可能原因: 词法解析器误判了有效输入，或者处理了预期不处理的错误", Fore.YELLOW)
                
                # 显示详细差异
                diffs = results.get('diffs', [])
                if diffs:
                    print_colored("  详细差异:", Fore.YELLOW)
                    
                    # 最多显示5个差异
                    for i, diff in enumerate(diffs[:5]):
                        line_num = diff.get('line_number')
                        expected = diff.get('expected')
                        actual = diff.get('actual')
                        
                        print_colored(f"  行 {line_num}:", Fore.YELLOW)
                        
                        if expected is not None:
                            print_colored(f"    预期: {expected}", Fore.GREEN)
                        else:
                            print_colored("    预期: (无)", Fore.GREEN)
                        
                        if actual is not None:
                            print_colored(f"    实际: {actual}", Fore.RED)
                        else:
                            print_colored("    实际: (无)", Fore.RED)
                    
                    if len(diffs) > 5:
                        print_colored(f"  (更多差异未显示...共 {len(diffs)} 处不同)", Fore.YELLOW)
                
                # 显示可能的问题原因
                problems = results.get('problems', [])
                if problems:
                    print_colored("  可能的问题原因:", Fore.YELLOW)
                    for problem in problems:
                        print_colored(f"  - {problem}", Fore.YELLOW)
    finally:
        # 清理临时文件
        shutil.rmtree(temp_dir)
    
    # 显示总结
    print_colored("=" * 40, Fore.CYAN)
    print_colored("测试完成!", Fore.CYAN)
    
    if passed_tests == total_tests:
        print_colored(f"通过: {passed_tests}/{total_tests}", Fore.GREEN)
    else:
        print_colored(f"通过: {passed_tests}/{total_tests}", Fore.YELLOW)
        print_colored(f"失败: {failed_tests}/{total_tests}", Fore.RED)
    
    # 如果有失败的测试，提供修复建议
    if failed_tests > 0:
        print_colored("\n修复建议:", Fore.YELLOW)
        print_colored("1. 检查输出格式是否符合要求，特别是标点符号和空格", Fore.YELLOW)
        print_colored("2. 确保词法错误信息的格式为 'Error type A at Line [lineNo]: [errorMessage]'", Fore.YELLOW)
        print_colored("3. 验证行号计算是否正确", Fore.YELLOW)
        print_colored("4. 检查是否正确识别了所有词法单元和错误", Fore.YELLOW)
        print_colored("5. 可以手动检查各个失败的测试用例来确定具体问题", Fore.YELLOW)

if __name__ == "__main__":
    main()